/**
 * Created by thomas on 27/01/14.
 */
import * as Cesium from '@cesiumjs';

var Graticule = (function() {
  function defaultValue(options, defaultOptions) {
    var newOptions = {},
      option;
    for (option in options) {
      newOptions[option] = options[option];
    }
    for (option in defaultOptions) {
      if (newOptions[option] === undefined) {
        newOptions[option] = defaultOptions[option];
      }
    }
    return newOptions;
  }

  function _(description, scene) {
    description = description || {};

    this._tilingScheme = description.tilingScheme || new Cesium.GeographicTilingScheme();

    this._color = description.color || Cesium.Color.ORANGE;
    this._fontColor = description.fontColor || Cesium.Color.WHITE;
    this._weight = description.weight || 0.8;
    this._zoomInterval = description.zoomInterval || [
      Cesium.Math.toRadians(0.05),
      Cesium.Math.toRadians(0.1),
      Cesium.Math.toRadians(0.2),
      Cesium.Math.toRadians(0.5),
      Cesium.Math.toRadians(1.0),
      Cesium.Math.toRadians(2.0),
      Cesium.Math.toRadians(5.0),
      Cesium.Math.toRadians(10.0)
    ];

    this._tileWidth = description.tileWidth || 256;
    this._tileHeight = description.tileHeight || 256;

    this._ready = true;

    // 默认为十进制间隔
    this._sexagesimal = description.sexagesimal || false;
    this._numLines = description.numLines || 50;

    this._scene = scene;
    this._label = !!description.label;
    this._labels = new Cesium.LabelCollection();
    scene.primitives.add(this._labels);
    this._polylines = new Cesium.PolylineCollection();

    scene.primitives.add(this._polylines);
    this._ellipsoid = scene.globe.ellipsoid;

    var canvas = document.createElement('canvas');
    canvas.width = 256;
    canvas.height = 256;
    this._canvas = canvas;
  };

  var definePropertyWorks = (function() {
    try {
      return 'x' in Object.defineProperty({}, 'x', {});
    } catch (e) {
      return false;
    }
  })();

  /**
   * Defines properties on an object, using Object.defineProperties if available,
   * otherwise returns the object unchanged.  This function should be used in
   * setup code to prevent errors from completely halting JavaScript execution
   * in legacy browsers.
   *
   * @private
   *
   * @exports defineProperties
   */
  var defineProperties = Object.defineProperties;
  if (!definePropertyWorks || !defineProperties) {
    defineProperties = function(o) {
      return o;
    };
  }

  _.prototype.setLayer  = function (lyr) {
    const _this = this;
    if (lyr) {
      Object.defineProperty(lyr, "show", {
        get() {
          return _this._show
        },
        set(val) {
          _this.setVisible(val)
        }
      })
    }
  }

  defineProperties(_.prototype, {

    url: {
      get: function() {
        return undefined;
      }
    },

    proxy: {
      get: function() {
        return undefined;
      }
    },

    tileWidth: {
      get: function() {
        return this._tileWidth;
      }
    },

    tileHeight: {
      get: function() {
        return this._tileHeight;
      }
    },
    zoomInterval: {
      get: function() {
        return this._zoomInterval;
      }
    },

    maximumLevel: {
      get: function() {
        return 18;
      }
    },

    minimumLevel: {
      get: function() {
        return 0;
      }
    },
    tilingScheme: {
      get: function() {
        return this._tilingScheme;
      }
    },
    rectangle: {
      get: function() {
        return this._tilingScheme.rectangle;
      }
    },
    tileDiscardPolicy: {
      get: function() {
        return undefined;
      }
    },
    errorEvent: {
      get: function() {
        return this._errorEvent;
      }
    },
    ready: {
      get: function() {
        return this._ready;
      }
    },
    credit: {
      get: function() {
        return this._credit;
      }
    },
    hasAlphaChannel: {
      get: function() {
        return true;
      }
    },
    label: {
      get: function() {
        return this._label;
      },
      set(val){
        this._label = val;
        if(!val){
          this._labels.removeAll();
        }
      }
    }
  });

  _.prototype.makeLabel = function(lng, lat, text, top, color) {
    if(!this.label) return;
    this._labels.add({
      position: this._ellipsoid.cartographicToCartesian(new Cesium.Cartographic(lng, lat, 10.0)),
      text,
      font: '14px normal', //文字字体
      fillColor: this._fontColor,
      // outlineColor: 'white',
      outlineColor: Cesium.Color.BLACK,
      style: Cesium.LabelStyle.FILL, //文字显示风格
      style: Cesium.LabelStyle.FILL_AND_OUTLINE,
      // pixelOffset: new Cesium.Cartesian2(5, top ? 5 : -5),
      // eyeOffset: Cesium.Cartesian3.ZERO,
      horizontalOrigin: Cesium.HorizontalOrigin.RIGHT,
      horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
      verticalOrigin: top ? Cesium.VerticalOrigin.BOTTOM : Cesium.VerticalOrigin.TOP,
      verticalOrigin: Cesium.VerticalOrigin.CENTER,
      scale: 1.0
    });

  };

  _.prototype._drawGrid = function(extent) {
    var mins = this._zoomInterval;
    if (this._currentExtent && this._currentExtent.equals(extent)) {
      return;
    }
    this._currentExtent = extent;

    this._polylines.removeAll();
    this._labels.removeAll();

    var minPixel = 0;
    var maxPixel = this._canvasSize;

    var dLat = 0,
      dLng = 0,
      index;
    // get the nearest to the calculated value
    const {
      north,
      south,
      east,
      west
    } = extent;
    for (index = 0; index < mins.length && dLat < ((north - south) / 10); index++) {
      dLat = mins[index];
    }
    for (index = 0; index < mins.length && dLng < ((east - west) / 10); index++) {
      dLng = mins[index];
    }
    // round iteration limits to the computed grid interval
    var minLng = (west < 0 ? Math.ceil(west / dLng) : Math.floor(west / dLng)) * dLng;
    var minLat = (south < 0 ? Math.ceil(south / dLat) : Math.floor(south / dLat)) * dLat;
    var maxLng = (east < 0 ? Math.ceil(east / dLat) : Math.floor(east / dLat)) * dLat;
    var maxLat = (north < 0 ? Math.ceil(north / dLng) : Math.floor(north / dLng)) * dLng;

    // extend to make sure we cover for non refresh of tiles
    minLng = Math.max(minLng - 2 * dLng, -Math.PI);
    maxLng = Math.min(maxLng + 2 * dLng, Math.PI);
    minLat = Math.max(minLat - 2 * dLat, -Math.PI / 2);
    maxLat = Math.min(maxLat + 2 * dLng, Math.PI / 2);

    var ellipsoid = this._ellipsoid;
    var lat, lng, granularity = Cesium.Math.toRadians(1);

    // labels positions
    var latitudeText = minLat + Math.floor((maxLat - minLat) / dLat / 2) * dLat;
    for (lng = minLng; lng < maxLng; lng += dLng) {
      // draw meridian
      var path = [];
      for (lat = minLat; lat < maxLat; lat += granularity) {
        path.push(new Cesium.Cartographic(lng, lat))
      }
      path.push(new Cesium.Cartographic(lng, maxLat));
      this._polylines.add({
        positions: ellipsoid.cartographicArrayToCartesianArray(path),
        width: this._weight,
        material: new Cesium.Material({
          fabric: {
            type: 'Color',
            uniforms: {
              color: this._color
            }
          }
        })
      });

      var degLng = Cesium.Math.toDegrees(lng);
      const txt = this._sexagesimal ? this._decToSex(degLng) : degLng.toFixed(gridPrecision(dLng));
      this.makeLabel(lng, latitudeText, txt, false);
    }

    // lats
    var longitudeText = minLng + Math.floor((maxLng - minLng) / dLng / 2) * dLng;
    for (lat = minLat; lat < maxLat; lat += dLat) {
      // draw parallels
      var path = [];
      for (lng = minLng; lng < maxLng; lng += granularity) {
        path.push(new Cesium.Cartographic(lng, lat))
      }
      path.push(new Cesium.Cartographic(maxLng, lat));

      const color = this._color;
      // const material = new Cesium.PolylineGlowMaterialProperty({
      //   glowPower: 0.25,
      //   color
      // })
      const material = new Cesium.Material({
        fabric: {
          type: 'Color',
          uniforms: {
            color
          }
        }
      })
      this._polylines.add({
        positions: ellipsoid.cartographicArrayToCartesianArray(path),
        width: this._weight,
        material,
      });
      var degLat = Cesium.Math.toDegrees(lat);
      const txt = this._sexagesimal ? this._decToSex(degLat) : degLat.toFixed(gridPrecision(dLat));
      this.makeLabel(longitudeText, lat, txt, true);
    }
  };

  _.prototype.requestImage = function(x, y, level, request) {

    if (this.isVisible()) {
      this._drawGrid(this._getExtentView());
    }

    return this._canvas;
  };

  _.prototype.setVisible = function(visible) {
    this._show = visible;
    if (!visible) {
      this._polylines.removeAll();
      this._labels.removeAll();
    } else {
      this._currentExtent = null;
      this._drawGrid(this._getExtentView());
    }
  }

  _.prototype.isVisible = function() {
    return this._show;
    // return this._show && this.layer.show;
  }

  _.prototype._decToSex = function(d) {
    var degs = Math.floor(d);
    var mins = ((Math.abs(d) - degs) * 60.0).toFixed(2);
    if (mins == "60.00") {
      degs += 1.0;
      mins = "0.00";
    }
    return [degs, ":", mins].join('');
  };

  _.prototype._getExtentView = function() {
    const {
      _scene,
      _ellipsoid
    } = this;
    const {
      camera,
      canvas
    } = _scene;
    const {
      width,
      height
    } = canvas;
    var corners = [
      camera.pickEllipsoid(new Cesium.Cartesian2(0, 0), _ellipsoid), //选择一个椭球或地图。
      camera.pickEllipsoid(new Cesium.Cartesian2(width, 0), _ellipsoid),
      camera.pickEllipsoid(new Cesium.Cartesian2(0, height), _ellipsoid),
      camera.pickEllipsoid(new Cesium.Cartesian2(width, height), _ellipsoid)
    ];
    for (var index = 0; index < 4; index++) {
      if (corners[index] === undefined) {
        return Cesium.Rectangle.MAX_VALUE;
      }
    }
    return Cesium.Rectangle.fromCartographicArray(_ellipsoid.cartesianArrayToCartographicArray(corners));
  }

  function gridPrecision(dDeg) {
    if (dDeg < 0.01) return 3;
    if (dDeg < 0.1) return 2;
    if (dDeg < 1) return 1;
    return 0;
  }
  function gridPrecision(dDeg) {
    // if (dDeg < 0.01) return 3;
    if (dDeg < 0.1) return 2;
    if (dDeg < 1) return 1;
    return 0;
  }

  function loggingMessage(message) {
    const logging = document.getElementById('logging');
    logging && (logging.innerHTML += message);
  }

  return _;

})();


export default Graticule;
